#include "soundin.h"
#include <stdexcept>

extern "C" {
extern struct {
  double d8[4*60*96000];   // This is "common/datcom/..." in fortran
  double fcenter;
  int nutc;
  int newdat2;
  int kbuf;
  int kxp;
  int kk;
  int kkdone;
  int nlost;
  int nlen;
  char fname80[80];
} datcom_;
}

QString reportAudioError(QAudio::Error audioError)
{
  switch (audioError) {
  case QAudio::NoError: Q_ASSERT(false);
  case QAudio::OpenError: return QObject::tr(
          "An error opening the audio device has occurred.");
  case QAudio::IOError: return QObject::tr(
          "An error occurred during read/write of audio device.");
  case QAudio::UnderrunError: return QObject::tr(
          "Audio data not being fed to the audio device fast enough.");
  case QAudio::FatalError: return QObject::tr(
          "Non-recoverable error, audio device not usable at this time.");
  }
  Q_ASSERT(false);
  return "";
}

//--------------------------------------------------------------------------
// SoundInThread
//--------------------------------------------------------------------------
void SoundInThread::run()
{
  quitExecutionMutex.lock();
  quitExecution = false;
  quitExecutionMutex.unlock();

  m_net = true;                         //### Hardwired! ###

  if (m_net) {
    inputUDP();
    return;
  }

  const char* pcmCodec = "audio/pcm";
  QAudioFormat audioFormat = inputDevice.preferredFormat();
  audioFormat.setChannels(1);
  audioFormat.setCodec(pcmCodec);
  audioFormat.setFrequency((int) m_rate);
  audioFormat.setSampleType(QAudioFormat::SignedInt);
  audioFormat.setSampleSize(16);

  if (!audioFormat.isValid()) {
    emit error(tr("Requested audio format is not available."));
    return;
  }

  QAudioInput audioInput(inputDevice, audioFormat);
  if (audioInput.error() != QAudio::NoError) {
    emit error(reportAudioError(audioInput.error()));
    return;
  }

  std::valarray<qint16> valarrayBuffer(bufSize);
  std::valarray<qint16> buffer2(bufSize);
  size_t valarrayBufferSamples = 0;   // how many buffer samples are filled.

  QIODevice* stream = audioInput.start();
  bool qe = quitExecution;

  // This is the main audio input loop:
  while (!qe) {
    quitExecutionMutex.lock();
    qe = quitExecution;
    quitExecutionMutex.unlock();
    if (qe) break;

    // Error checking...
    if (audioInput.error() != QAudio::NoError) {
      emit error(reportAudioError(audioInput.error()));
      return;
    }

    // How many new samples have been acquired?
    const qint64 bytesReady = audioInput.bytesReady();
    Q_ASSERT(bytesReady >= 0);
    Q_ASSERT(bytesReady % 2 == 0);
    if (bytesReady == 0) {
      msleep(50);
      continue;
    }

    // Get the new samples
    qint64 bytesRead;
    if (bytesReady/2 + valarrayBufferSamples <= bufSize) {
      bytesRead = stream->read(
            (char*) &valarrayBuffer[valarrayBufferSamples], bytesReady);
      // we check the error case (bytesRead == -1) a few lines below
      Q_ASSERT(bytesRead <= bytesReady);
    } else {
      int bytesToRead = (bufSize-valarrayBufferSamples)*2;
      Q_ASSERT(bytesToRead >= 0);

      bytesRead = stream->read(
            (char*) &valarrayBuffer[valarrayBufferSamples], bytesToRead);
      // we check the error case (bytesRead == -1) a few lines below
      Q_ASSERT(bytesRead <= bytesToRead);
    }
    if (bytesRead < 0) {
      emit error(tr("audio stream QIODevice::read returned -1."));
      return;
    }
    Q_ASSERT(bytesRead % 2 == 0);

    valarrayBufferSamples += bytesRead / 2;

    // If we don't have enough data: wait...
    if (valarrayBufferSamples < bufSize) {
      msleep(50);
      continue;
    }

    // OK, our buffer is full: copy and report it
    for (uint i=0; i<valarrayBufferSamples; i++) {
      buffer2[i]=valarrayBuffer[i];
    }
    emit bufferAvailable(buffer2, audioFormat.frequency());
    valarrayBufferSamples = 0;
  }
}

void SoundInThread::setInputDevice(const QAudioDeviceInfo& inputDevice)
{
  if (isRunning()) return;
  this->inputDevice = inputDevice;
}


void SoundInThread::setRate(double rate)
{
  if (isRunning()) return;
  this->m_rate = rate;
}

void SoundInThread::setBufSize(unsigned n)
{
  if (isRunning()) return;
  this->bufSize = n;
}

void SoundInThread::quit()
{
  quitExecutionMutex.lock();
  quitExecution = true;
  quitExecutionMutex.unlock();
}

void SoundInThread::setNetwork(bool b)
{
  m_net = b;
}

void SoundInThread::inputUDP()
{
  static bool first=true;

  udpSocket = new QUdpSocket();
  if(!udpSocket->bind(50004,QUdpSocket::ShareAddress) )
  {
    qDebug()<<"UDP Bind Failed";
  }

  // Set this socket's total buffer space for received UDP packets
  int v=141600;
  ::setsockopt(udpSocket->socketDescriptor(), SOL_SOCKET, SO_RCVBUF,
               (char *)&v, sizeof(v));

  bool qe = quitExecution;
  struct linradBuffer {
    double cfreq;
    int msec;
    float userfreq;
    int iptr;
    quint16 iblk;
    qint8 nrx;
    char iusb;
    double d8[174];
  } b;

//  quint16 iblk0=0;
  int n60z=99;
  int lauto=1;
  int monitoring=1;
  int k=0;
  int kb=0;
  int ntx=0;                    //Negative indicates floating-point data
  int lostTot=0;
  int nsec;
  int n60;
  int nseclr;
  int nsec0=-999;
  int nhsym0=0;
  bool synced=false;
  int iz;

  std::valarray<qint16> buffer1(bufSize);
  std::valarray<qint16> buffer2(bufSize);
  std::valarray<qint16> buffer3(bufSize);

  // This is the network input loop.  Much of this code was translated
  // from MAP65 subroutine recvpkt.
  while (!qe) {
    quitExecutionMutex.lock();
    qe = quitExecution;
    quitExecutionMutex.unlock();
    if (qe) break;

    if (!udpSocket->hasPendingDatagrams()) {
      msleep(2);            // Sleep if no packet available
    } else {
      int nBytesRead = udpSocket->readDatagram((char *)&b,1416);
      if (nBytesRead != 1416) qDebug() << "UDP Read Error:" << nBytesRead;
      qint64 ms = QDateTime::currentMSecsSinceEpoch() % 86400000;
      nsec = ms/1000;             // Time according to this computer
      n60 = nsec % 60;
      nseclr = b.msec/1000;       // Time from the UDP packet

      if(b.nrx>=0) {
        iz=174;
      } else {
        iz=87;
      }

// Reset buffer pointers at start of minute
      if ((n60 < n60z) && (lauto+monitoring != 0)) {
        if (ntx == 0) kb=1-kb;               // switch buffers
        if(first) kb=0;
        k=kb*60*96000;
        datcom_.kxp=k;
        lostTot=0;
        synced=true;
        ntx=0;
        first=false;
      }
      n60z=n60;

//      if (transmitting==1) ntx=1;

// Save data if buffer is not already full
      if ((kb==0 && (k+iz) <= 60*96000) ||
          (kb==1 && (k+iz) <= 2*60*96000)) {
        recvpkt_(&b.iblk,&b.nrx,&kb,&k,b.d8,b.d8);
      }

      int nhsym=(k-kb*60*96000)/17832.9252;
      if (nhsym != nhsym0) {
        emit readyForFFT(kb,k);
        nhsym0=nhsym;
      }

      if (nsec != nsec0) {
// If we have not transmitted in this minute, see if it's time to decode
        if (ntx==0 && (lauto+monitoring>0)) {
          datcom_.fcenter=b.cfreq;
          int mutch=nseclr/3600;
          datcom_.nutc=100*mutch + (nseclr/60) % 60;
          datcom_.kbuf=kb;
          datcom_.kk=k;
          datcom_.nlost=lostTot;
//        ndiskdat=0;
//        ndone2=1;
        }
        nsec0=nsec;
      }
    }
  }
}  // Returns only when told to quit
